package com.klondike.common.io;

import static com.klondike.common.Constants.UTF_8;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.File;
import java.io.FileFilter;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.net.URL;
import java.util.Enumeration;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

import org.junit.Test;
import org.mockito.Matchers;
import org.mockito.Mockito;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;

import com.klondike.common.ObjectReference;
import com.klondike.common.Utils;

public class TheIOUtils {

    @Test
    public void loadAndReadTheContentOfResourceAndCloseTheUnderlyingReader() throws Exception {
        final Reader underlyingReader = IOUtils.getResourceAsReader(TheIOUtils.class, "TheIOUtils.txt");
        final ObjectReference<Boolean> closeFlag = new ObjectReference<Boolean>(Boolean.FALSE);

        Reader reader = spy(underlyingReader);

        spyCloseOperation(closeFlag, reader);

        assertNotNull(reader);

        String s = IOUtils.toString(reader);

        assertEquals("Sample Test Resource.", s);
        assertTrue(closeFlag.get());

    }

    @Test(expected = IOException.class)
    public void throwIOExceptionOnNonExistsResorce() throws Exception {
        IOUtils.getResourceAsReader(TheIOUtils.class, "some_invallid_resource");
    }

    @Test
    public void doNotThrowExceptionFromCloseQuietly() throws Exception {
        Closeable closeable = Mockito.mock(Closeable.class);
        doThrow(new IOException()).when(closeable).close();

        IOUtils.closeQuietly(closeable);
        IOUtils.closeQuietly(null);
    }

    @Test
    public void copyAndCloseDataStream() throws Exception {
        String expected = "SOME_DATA";

        ByteArrayInputStream bin = new ByteArrayInputStream(expected.getBytes(UTF_8));
        ByteArrayOutputStream bout = new ByteArrayOutputStream();

        bin = spy(bin);
        bout = spy(bout);

        ObjectReference<Boolean> binCloseFlag = new ObjectReference<Boolean>(Boolean.FALSE);
        ObjectReference<Boolean> boutCloseFlag = new ObjectReference<Boolean>(Boolean.FALSE);

        spyCloseOperation(binCloseFlag, bin);
        spyCloseOperation(boutCloseFlag, bout);

        IOUtils.copyAndClose(bin, bout);

        String actual = bout.toString(UTF_8);

        assertEquals(expected, actual);
        assertTrue(binCloseFlag.get());
        assertTrue(boutCloseFlag.get());
    }

    @Test
    public void copyAndCloseInDataStream() throws Exception {
        String expected = "SOME_DATA";

        ByteArrayInputStream bin = new ByteArrayInputStream(expected.getBytes(UTF_8));
        ByteArrayOutputStream bout = new ByteArrayOutputStream();

        bin = spy(bin);
        bout = spy(bout);

        ObjectReference<Boolean> binCloseFlag = new ObjectReference<Boolean>(Boolean.FALSE);
        ObjectReference<Boolean> boutCloseFlag = new ObjectReference<Boolean>(Boolean.FALSE);

        spyCloseOperation(binCloseFlag, bin);
        spyCloseOperation(boutCloseFlag, bout);

        IOUtils.copyAndCloseIn(bin, bout);

        String actual = bout.toString(UTF_8);

        assertEquals(expected, actual);
        assertTrue(binCloseFlag.get());
        assertFalse(boutCloseFlag.get());
    }

    @Test
    public void fileNameSuffixFileFilter() throws Exception {
        File file = spy(new File("_mock_"));
        String[] fileNames = { "A.java", "B.java", "readme.txt", "readmeOld_txt", "data.ser" };

        Mockito.when(file.list()).thenReturn(fileNames);

        File[] files = file.listFiles(IOUtils.fileNameSuffixFilter(".txt,.java"));

        assertEquals(3, files.length);
    }

    @Test
    public void fileExtn() throws Exception {
        assertEquals(".java", IOUtils.fileExtn("Welcome.java"));
        assertEquals("", IOUtils.fileExtn("SOME_DATA_FILE"));
    }

    @Test
    public void escapeFileName() throws Exception {
        assertEquals("The File Name$.java", IOUtils.escapeFileName("The\\File/Name$.java"));
    }

    @Test
    public void writeToFileAndReadBack() throws Exception {
        File file = File.createTempFile("_demo", ".txt");
        String expected = "Java is a OO Programming Language";

        IOUtils.writeTo(expected, file);
        String actaul = IOUtils.toString(new FileReader(file));

        assertEquals(expected, actaul);
    }

    @Test
    public void traversTempDirectory() throws Exception {
        final File file = File.createTempFile("_demo", ".txt");
        FileVisitor visitor = Mockito.mock(FileVisitor.class);

        Mockito.when(visitor.getFilter()).thenReturn(null);
        final ObjectReference<Boolean> isFileVisited = new ObjectReference<Boolean>(Boolean.FALSE);

        Mockito.doAnswer(new Answer<Void>() {

            @Override
            public Void answer(InvocationOnMock invocation) throws Throwable {
                File visitedFile = (File) invocation.getArguments()[0];
                if (!isFileVisited.get()) {
                    isFileVisited.set(visitedFile.equals(file));
                }

                return null;
            }

        }).when(visitor).visit(Matchers.any(File.class));

        IOUtils.traverse(file.getParentFile(), visitor);
        isFileVisited.set(false);
        Mockito.when(visitor.getFilter()).thenReturn(IOUtils.fileNameSuffixFilter(".txt"));
        IOUtils.traverse(file.getParentFile(), visitor);

        assertTrue(isFileVisited.get());

    }

    @Test
    public void createAZipFile() throws Exception {
        String content = "Java is a OO Programming Language.Java is a OO Programming Language";
        File dir = createATempDirecotyWithSingleFileHavingContent(content);

        File zipFile = File.createTempFile("_demoZip", ".zip");

        IOUtils.createZip(dir, zipFile);

        ZipFile newZipFile = new ZipFile(zipFile);
        Enumeration<? extends ZipEntry> entries = newZipFile.entries();

        assertTrue(entries.hasMoreElements());
        ZipEntry nextElement = entries.nextElement();

        InputStream stream = newZipFile.getInputStream(nextElement);
        String data = IOUtils.toString(new InputStreamReader(stream));

        IOUtils.closeQuietly(stream);

        assertEquals("Data is not same.", content, data);

        File compressedZipFile = File.createTempFile("_compressedDemoZip", ".zip");
        IOUtils.createZip(dir, compressedZipFile, null, 5, ZipEntry.DEFLATED);
        // assertTrue(zipFile.length() +" is not less than "
        // +compressedZipFile.length(), zipFile.length() <
        // compressedZipFile.length());
    }

    private File createATempDirecotyWithSingleFileHavingContent(String expected) throws IOException {
        File dir = new File(System.getProperty("java.io.tmpdir"), "_" + System.nanoTime() + "__temp");
        dir.mkdir();
        File file = new File(dir, "_demo.txt");

        IOUtils.writeTo(expected, file);
        return dir;
    }

    private void spyCloseOperation(final ObjectReference<Boolean> closeFlag, Closeable closeable) throws IOException {
        doAnswer(new Answer<Void>() {

            @Override
            public Void answer(InvocationOnMock invocation) throws Throwable {
                invocation.callRealMethod();
                closeFlag.set(Boolean.TRUE);
                return null;
            }

        }).when(closeable).close();
    }

    @Test
    public void closeQuietly() throws Exception {
        Closeable closeable = null;

        IOUtils.closeQuietly(closeable);

        closeable = Mockito.mock(Closeable.class);

        doNothing().when(closeable).close();

        IOUtils.closeQuietly(closeable);

        doThrow(new IOException("can not close")).when(closeable).close();

        IOUtils.closeQuietly(closeable);
    }

    @Test
    public void listReadOnlyFiles() throws Exception {
        FileFilter readOnlyFileFilter = IOUtils.readOnlyFileFilter();

        File givenReadOnlyFile = mock(File.class);
        when(givenReadOnlyFile.isDirectory()).thenReturn(false);
        when(givenReadOnlyFile.canWrite()).thenReturn(false);
        when(givenReadOnlyFile.canRead()).thenReturn(true);

        File givenReadWritableFile = mock(File.class);
        when(givenReadWritableFile.isDirectory()).thenReturn(false);
        when(givenReadWritableFile.canWrite()).thenReturn(true);
        when(givenReadWritableFile.canRead()).thenReturn(true);

        File givenNeitherReadNorWritableFile = mock(File.class);
        when(givenNeitherReadNorWritableFile.isDirectory()).thenReturn(false);
        when(givenNeitherReadNorWritableFile.canWrite()).thenReturn(false);
        when(givenNeitherReadNorWritableFile.canRead()).thenReturn(false);

        assertEquals(false, readOnlyFileFilter.accept(givenNeitherReadNorWritableFile));

        File givenDirectory = mock(File.class);
        when(givenDirectory.isDirectory()).thenReturn(true);
        when(givenDirectory.canRead()).thenReturn(true);

        assertEquals(true, readOnlyFileFilter.accept(givenDirectory));
        assertEquals(true, readOnlyFileFilter.accept(givenReadOnlyFile));
        assertEquals(false, readOnlyFileFilter.accept(givenReadWritableFile));

    }

    @Test
    public void newBufferReaderTest() throws Exception {
        String s = "Java is a OO Programming Language";
        BufferedReader bufferedReader = IOUtils.newBufferedReader(IOUtils.asReader(s));
        String line = bufferedReader.readLine();

        assertEquals(s, line);
    }

    @Test
    public void testJarFileVisitor() throws Exception {
        URL junitJarUrl = Utils.getSourceUrlFor(Test.class);
        final String testClassName = "Test.class";
        final ObjectReference<Boolean> booleanHolder = new ObjectReference<Boolean>();
        FileVisitor visiter = new FileVisitor.JarEntryVisitor() {

            @Override
            protected void visit(JarFile jarFile, JarEntry e) throws IOException {
                if (e.getName().endsWith(testClassName)) {
                    booleanHolder.set(Boolean.TRUE);
                }
            }
        };
        File junitJarFile = new File(ResourceUtils.toURI(junitJarUrl));

        visiter.visit(junitJarFile);
        
        assertTrue(visiter.getFilter().accept(junitJarFile));
        assertTrue(booleanHolder.get());
    }
}
